package cn.song.listener;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;

import cn.song.controller.common.CommonService;
import cn.song.entity.easyexcel.ExcelModel;
import cn.song.exception.CustomException;
import cn.song.model.DownloadDto;
import cn.song.util.easyexcel.DownloadUtil;
import cn.song.util.easyexcel.model.BatchCount;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;

import lombok.extern.slf4j.Slf4j;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 文件上传的读取类 - 批量导单
 *
 * @author SongRenShuo
 */
// 有个很重要的点 ExcelSuperListener 不能被spring管理，要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class ExcelOrderImportListener implements ReadListener<ExcelModel> {

    /**
     * 每隔50条存储数据库，实际使用中可以100条，然后清理list ，方便内存回收
     */
    private static final int BATCH_COUNT = 100;

    private List<ExcelModel> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    private final BatchCount batchCount;
    private final DownloadDto downloadDto;
    private final DownloadUtil downloadUtil;
    /**
     * 假设这个是一个DAO，当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private final CommonService commonService;

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     */
    public ExcelOrderImportListener(CommonService service, DownloadUtil downloadUtil, DownloadDto downloadDto, BatchCount batchCount) {
        this.commonService = service;
        this.downloadUtil = downloadUtil;
        this.downloadDto = downloadDto;
        this.batchCount = batchCount;

    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. It is same as {@link AnalysisContext#readRowHolder()}
     * @param context 上下文
     * @author SongRenShuo
     */
    @Override
    public void invoke(ExcelModel data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSONUtil.toJsonStr(data));
        validate(data);
        cachedDataList.add(data);
        // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            try {
                saveData();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context 上下文
     * @author SongRenShuo
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据，确保最后遗留的数据也存储到数据库
        try {
            saveData();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        log.info("所有数据解析完成！");
    }


    /**
     * 异常方法 (类型转换异常也会执行此方法)  （读取一行抛出异常也会执行此方法)
     *
     * @param context 上下文
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("文件上传 监听器有异常：{}", exception.getMessage(), exception);
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;

            // 获取具体的单元格数据
            CellData<?> cellData = excelDataConvertException.getCellData();
            // 假设我们要的是字符串值
            String cellValue = cellData.getStringValue();

            String value = cellValue != null ? cellValue : "无法解析的数据";
            log.error("文件上传 第{}行，第{}列解析异常，数据为:{}",
                    excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(),
                    value);

            String message = StrUtil.format("第【{}】行,第【{}】列格式错误,异常数据【{}】"
                    , excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex() + 1, value);
            throw new RuntimeException(message);
        } else if (exception instanceof CustomException) {
            ExcelModel excelModel = (ExcelModel) context.readRowHolder().getCurrentRowAnalysisResult();

            String orderIdStr = StrUtil.format("序号【{}】 代发订单号【{}】", excelModel.getId());

            // batchCount.setSuccessCount(batchCount.getSuccessCount() + insert.getSuccessCount());
            batchCount.setErrorCount(batchCount.getErrorCount() + 1);
            batchCount.getErrorMsg().add(BatchCount.BatchCountDto.builder().orderId(orderIdStr).state("error").msg("失败，订单号：" + orderIdStr + ",具体原因：" + exception.getMessage()).build());
            downloadDto.setTotal(batchCount.getSuccessCount() + batchCount.getErrorCount());
            downloadUtil.updateDownloadList(downloadDto);

            log.error("文件上传 数据异常 订单号：{} 具体原因：{}",orderIdStr, exception.getMessage());
        }
    }

    /**
     * Valid.
     *
     * @param data    the data
     */
    protected final void validate(final ExcelModel data) {
        // 过滤掉信息不合格的数据
        try (ValidatorFactory factory = Validation.buildDefaultValidatorFactory()) {
            Validator validator = factory.getValidator();

            // 遍历集合，对每条导入数据进行校验
            Set<ConstraintViolation<ExcelModel>> violations = validator.validate(data);
            // 检查是否有错误，并获取错误信息
            if (violations.isEmpty()) {
                return;
            }

            List<String> errorMessageList = violations.stream()
                    .map(ConstraintViolation::getMessage)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
            throw new CustomException("数据错误：" + errorMessageList);
        }
    }


    /**
     * 加上存储数据库
     */
    private void saveData() throws IOException {
        log.info("{}条数据，开始存储数据库！", cachedDataList.size());
        // 这里开始储存数据库
        BatchCount insert = commonService.insertImport(cachedDataList, downloadDto);

        batchCount.setSuccessCount(batchCount.getSuccessCount() + insert.getSuccessCount());
        batchCount.setErrorCount(batchCount.getErrorCount() + insert.getErrorCount());
        batchCount.getErrorMsg().addAll(insert.getErrorMsg());
        batchCount.setTotalCount(batchCount.getSuccessCount() + batchCount.getErrorCount());
        downloadDto.setTotal(batchCount.getTotalCount());
        downloadUtil.updateDownloadList(downloadDto);

        log.info("上传结果:{}", JSONUtil.toJsonStr(batchCount));
    }


}


